import {
  ArgsTable,
  Canvas,
  Meta,
  Story,
} from '@storybook/addon-docs';
import { html } from 'lit';
import { ifDefined } from 'lit/directives/if-defined.js';
import { extraPadding } from '../../utilities/chromatic-decorators';

<Meta
  title="Components/Input"
  component="bl-input"
  decorators={[
    extraPadding,
  ]}
  argTypes={{
    label: {
      control: 'text',
    },
    type: {
      control: {
        type: 'select',
        options: ['text', 'email', 'date', 'time', 'datetime-local', 'month', 'week', 'password', 'number', 'tel', 'url']
      },
    },
    placeholder: {
      control: 'text',
    },
    value: {
      control: 'text',
    },
    required: {
      control: 'boolean',
    },
    minlength: {
      control: 'text',
      type: 'number'
    },
    maxlength: {
      control: 'text',
      type: 'number'
    },
    min: {
      control: 'text',
      type: 'number'
    },
    max: {
      control: 'text',
      type: 'number'
    },
    pattern: {
      control: 'text',
      type: 'string'
    },
    step: {
      control: 'text',
      type: 'number'
    },
    icon: {
      control: 'text',
      type: 'string'
    },
    size: {
      control: {
        type: 'select',
        options: ['small', 'medium', 'large']
      },
      type: 'string'
    },
    disabled: {
      control: 'boolean',
    },
    readonly: {
      control: 'boolean',
    },
    labelFixed: {
      control: 'boolean',
    },
    helpText: {
      control: 'text'
    },
    error: {
      control: 'text',
    },
  }}
/>

export const BlButton = args => html`
  <bl-button>Check</bl-button>`

export const SingleInputTemplate = (args) => html`<bl-input
    type='${ifDefined(args.type)}'
    label='${ifDefined(args.label)}'
    placeholder='${ifDefined(args.placeholder)}'
    value='${ifDefined(args.value)}'
    ?loading=${ifDefined(args.loading)}
    ?required='${args.required}'
    ?disabled='${args.disabled}'
    ?readonly='${args.readonly}'
    ?label-fixed='${args.labelFixed}'
    invalid-text='${ifDefined(args.customInvalidText)}'
    help-text='${ifDefined(args.helpText)}'
    minlength='${ifDefined(args.minlength)}'
    maxlength='${ifDefined(args.maxlength)}'
    min='${ifDefined(args.min)}'
    max='${ifDefined(args.max)}'
    pattern='${ifDefined(args.pattern)}'
    step='${ifDefined(args.step)}'
    icon='${ifDefined(args.icon)}'
    size='${ifDefined(args.size)}'
    error='${ifDefined(args.error)}'
  >${args.slot?.()}</bl-input>`

export const SizeVariantsTemplate = args => html`
${SingleInputTemplate({ size: 'large', ...args })}
${SingleInputTemplate({ size: 'medium', ...args })}
${SingleInputTemplate({ size: 'small', ...args })}
`

# Input

<bl-badge icon="document">ADR</bl-badge>
<bl-badge icon="puzzle">[Figma](https://www.figma.com/file/RrcLH0mWpIUy4vwuTlDeKN/Baklava-Design-Guide?node-id=4%3A5586)</bl-badge>
<bl-badge icon="check_fill">RTL Supported</bl-badge>

Input component is the component for taking text input from user.

## Basic Usage

Currently, input component supports `text`, `number` and `password` types, which default is `text`.

<Canvas>
  <Story name="Text Input" args={{ placeholder: 'Enter Your Name' }}>
    {SingleInputTemplate.bind({})}
  </Story>
  <Story name="Number Input" args={{ type: 'number', placeholder: 'Enter Your Age' }}>
    {SingleInputTemplate.bind({})}
  </Story>
  <Story name="Password Input" args={{ type: 'password', placeholder: 'Enter Your Password' }}>
    {SingleInputTemplate.bind({})}
  </Story>
</Canvas>

## Input Labels

Input optionally can have a `label`.
If the label is set, it will be a floating label by default.
If you want to use always it on top of the input, then you can use `label-fixed` attribute.

<Canvas isColumn>
  <Story name="Input With Label"
         args={{ label: 'User Name', placeholder: 'Enter Your Name' }}>
    {SingleInputTemplate.bind({})}
  </Story>
  <Story name="Input With Fixed Label" args={{
    label: 'User Name',
    labelFixed: true,
    placeholder: 'Enter Your Name'
  }}>
    {SingleInputTemplate.bind({})}
  </Story>
  <Story name="Input Without Label" args={{ placeholder: 'Enter Your Name' }}>
    {SingleInputTemplate.bind({})}
  </Story>
  <Story name="Input with value" args={{ label: 'Your name', placeholder: 'Name Surname', value: 'Random User' }}>
    {SingleInputTemplate.bind({})}
  </Story>
</Canvas>

Input component will cut-out long labels those doesn't width of input, with ellipsis char.

<Canvas isColumn>
  <Story name="Input With Long Label" args={{ label: "Very very long label that doesn't fit select component width" }}>
    {SingleInputTemplate.bind({})}
  </Story>
  <Story name="Input With Long Label with Icon" args={{ label: "Very very long label that doesn't fit select component width", icon: 'profile' }}>
    {SingleInputTemplate.bind({})}
  </Story>
  <Story name="Input With Fixed Long Label" args={{ label: "Very very long label that doesn't fit select component width", placeholder: "Username", labelFixed: true }}>
    {SingleInputTemplate.bind({})}
  </Story>
</Canvas>

## Input Help Text

You can give extra information to user with `help-text` attribute.

<Canvas>
  <Story name="Input Help Text"
         args={{ type: 'text', placeholder: 'Enter Name', helpText: 'Your username should include only letters.' }}>
    {SingleInputTemplate.bind({})}
  </Story>
</Canvas>

## Input With Icon

Input can have an icon. This icon is showed with `bl-icon` component internally and it's color synced with the state of input.

<Canvas>
  <Story name="Input With Icon"
         args={{ type: 'text', placeholder: 'Enter Name', required: true, icon: 'calendar' }}>
    {SingleInputTemplate.bind({})}
  </Story>
  <Story name="Password Input With Icon"
         args={{ type: 'password', placeholder: 'Enter Password', required: true, icon: 'lock' }}>
    {SingleInputTemplate.bind({})}
  </Story>
</Canvas>

Input also supports slot icons for more complex use cases. You can use `icon` slot for this.

<Canvas>
  <Story name="Input With Slot Icon"
        args={{ placeholder: 'Name', slot: () => html`<bl-icon slot="icon" name="flash"></bl-icon>` }}>
    {SingleInputTemplate.bind({})}
  </Story>
</Canvas>

Inputs with type of date, time, datetime-local, month, week and search have default icons. You can override these icons with `icon` attribute.

<Canvas>
  <Story name="Date Input With Default Icon" args={{ type: "date" }}>
    {SingleInputTemplate.bind({})}
  </Story>
  <Story name="Time Input With Default Icon" args={{ type: "time" }}>
    {SingleInputTemplate.bind({})}
  </Story>
  <Story name="Search Input With Default Icon" args={{ type: "search" }}>
    {SingleInputTemplate.bind({})}
  </Story>
  <Story name="Date Input With Custom Icon" args={{ type: "date", icon: "star" }}>
    {SingleInputTemplate.bind({})}
  </Story>
</Canvas>


## Input Validation

Input supports native HTML validation rules like `required`, `minlength`, `maxlength`,  `min` and `max`. Other validation rules will come soon.

Input validation will run after user enters a value and go out from the input. If there is a validation issue, input will be highlighted in error state. After this state every change will have immediate effect on input to update validation state.

<Canvas>
  <Story name="Validation with Text Input"
         args={{ type: 'text', label: 'User Name', minlength: 5, maxlength: 20, required: true }}
  >
    {SingleInputTemplate.bind({})}
  </Story>
    <Story name="Validation with Number Input"
         args={{ type: 'number', label: 'Age', min: 18, required: true }}
  >
    {SingleInputTemplate.bind({})}
  </Story>
</Canvas>

### Custom Error Text

Validation error messages are used from default browser error messages by default. If you want to override, you can do it in a native-like structure as below.

```html
<bl-input id="input" required />

<script>
  const blInput = document.getElementById("input");
  blInput.addEventListener("bl-input", (e) => {
    if(e.target.validity.valueMissing){
      e.target.setCustomValidity("Custom Error Text");
    }else{
      e.target.setCustomValidity("");
    }
  });
</script>
```

### Custom Validation

If you want to use a different validation than all validations, you can do this with the `error` attribute. *Native validators will always be superior to custom errors.*

<bl-alert icon variant="warning">When you use this attribute, the `dirty` prop will instantly become true.</bl-alert>

<Canvas>
  <Story name="Custom Validation"
         args={{ type: 'text', label: 'User Name', error: 'I am custom validation' }}
  >
    {SingleInputTemplate.bind({})}
  </Story>
</Canvas>

## Input Sizes

Inputs have 3 size options: `large`, `medium` and `small`. `medium` size is default and if you want to show a large or small input you can set `size` attribute.

<Canvas>
  <Story name="Input sizes without value"
         args={{ type: 'text', label: 'User Name', icon: 'profile' }}
  >
    {SizeVariantsTemplate.bind({})}
  </Story>
  <Story name="Input sizes with value"
         args={{ type: 'text', label: 'User Name', value: 'excalibur82' }}
  >
    {SizeVariantsTemplate.bind({})}
  </Story>
</Canvas>

## Disabled Input

Input can be set as disabled by adding `disabled` attribute.

<Canvas isColumn>
  <Story name="Disabled Input with label"
         args={{ type: 'text', label: 'User Name', disabled: true }}
  >
    {SingleInputTemplate.bind({})}
  </Story>
  <Story name="Disabled Input with placeholder"
         args={{ type: 'text', label: 'User Name', labelFixed: true, placeholder: 'namesurname', disabled: true }}
  >
    {SingleInputTemplate.bind({})}
  </Story>
  <Story name="Disabled Input with value"
         args={{ type: 'text', label: 'User Name', value: 'excalibur82', disabled: true }}
  >
    {SingleInputTemplate.bind({})}
  </Story>
  <Story name="Disabled Input with icon"
         args={{ type: 'text', label: 'User Name', icon: 'profile', disabled: true }}
  >
    {SingleInputTemplate.bind({})}
  </Story>
</Canvas>

## Search Input with Clear Button

Input with type `search` has a clear button by default. This button will be shown when input has a value. You can clear the input by clicking this button.
This is going trigger `input` event with empty string.

<Canvas>
  <Story name="Search Input with Clear Button" args={{ type: 'search', placeholder: 'Search', value: 'some input' }}>
    {SingleInputTemplate.bind({})}
  </Story>
</Canvas>

## Search Input with Loading Attribute

This example demonstrates how to use the `bl-spinner` component inside a search input field. The spinner is displayed to indicate that a search operation is in progress.
The `loading` attribute is set to `true` to show the spinner inside the input field.

<Canvas>
  <Story name="Input with Loading Spinner" args={{ placeholder: 'Search Loading Example', type: 'search', loading: true }}>
    {args => html`
      <bl-input
        id="searchInput"
        placeholder=${ifDefined(args.placeholder)}
        type=${ifDefined(args.type)}
        label=${ifDefined(args.label)}
        loading=${ifDefined(args.loading)}
      ></bl-input>
    `}
  </Story>
</Canvas>

## RTL Support

The input component supports RTL (Right-to-Left) text direction. You can enable RTL mode by setting the `dir` attribute on a parent element or the `html` tag.

<Canvas>
  <Story name="RTL Support">
    {() => html`
      <div style="display: flex; gap: 16px;">
        <div>
          <p style="margin-bottom: 8px;">LTR (Left-to-Right)</p>
          <bl-input
            label="Username"
            placeholder="Enter username"
            icon="profile"
          ></bl-input>
        </div>
        <div dir="rtl">
          <p style="margin-bottom: 8px;">RTL (Right-to-Left)</p>
          <bl-input
            label="اسم المستخدم"
            placeholder="أدخل اسم المستخدم"
            icon="profile"
          ></bl-input>
        </div>
      </div>
    `}
  </Story>
</Canvas>

Here's a vanilla JavaScript and HTML example of how to use RTL support:

```html
<!-- index.html -->
<html>
<head>
  <meta charset="UTF-8">
  <title>Baklava Input RTL Example</title>
  <!-- Import Baklava's CSS and JavaScript -->
  <script type="module" src="path/to/baklava.js"></script>
  <style>
    .container {
      margin: 20px;
    }
    .flex-container {
      display: flex;
      gap: 20px;
      margin-top: 20px;
    }
  </style>
</head>
<body>
  <div class="container">
    <button onclick="toggleDirection()">Toggle RTL/LTR</button>

    <div class="flex-container">
      <!-- LTR Example -->
      <div>
        <h3>LTR Input</h3>
        <bl-input
          label="Username"
          placeholder="Enter username"
          icon="profile"
        ></bl-input>
      </div>

      <!-- RTL Example -->
      <div dir="rtl">
        <h3>RTL Input</h3>
        <bl-input
          label="اسم المستخدم"
          placeholder="أدخل اسم المستخدم"
          icon="profile"
        ></bl-input>
      </div>
    </div>
  </div>

  <script>
    // Example of dynamically changing direction
    const toggleDirection = () => {
      const rtlContainer = document.querySelector('[dir="rtl"]');
      rtlContainer.dir = rtlContainer.dir === 'rtl' ? 'ltr' : 'rtl';
    };

    // Example of creating an input programmatically
    const createInput = (isRTL) => {
      const container = document.createElement('div');
      if (isRTL) container.dir = 'rtl';

      const input = document.createElement('bl-input');
      input.label = isRTL ? 'اسم المستخدم' : 'Username';
      input.placeholder = isRTL ? 'أدخل اسم المستخدم' : 'Enter username';
      input.icon = 'profile';

      container.appendChild(input);
      document.body.appendChild(container);
    };
  </script>
</body>
</html>
```

## Using within a form

Input component uses [ElementInternals](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) to associate with it's parent form automatically. When you use `bl-input` within a form with a `name` attribute, input's value will be automatically set parent form's FormData. Check the example below:

```html
<form novalidate>
  <bl-input name="name" label="Your Name"></bl-input>
  <bl-input name="age" type="number" required min="18" label="Age"></bl-input>

  <bl-button type="submit">Submit</bl-button>
</form>

<script>
  document.querySelector('form').addEventListener('submit', (event) => {
    event.preventDefault();
    const formData = new FormData(event.target);

    for (const [key, value] of formData.entries()) {
      console.log(key, value);
    }
  });
</script>
```

When you run this example and submit the form, you'll see key/value pairs of the inputs in the console.

<bl-alert icon>If user presses `Enter` key in an input inside a form, this will trigger submit of the form. This behaviour mimics the native input behaviour.</bl-alert>

If you want to prevent input to submit the form, you can listen `keydown` event and prevent it's default behaviour.

```js
document.querySelector('bl-input').addEventListener('keydown', (event) => {
  if (event.code === "Enter") {
    event.preventDefault();
  }
});
```

## Input Masking

Input masking is essential in user interfaces that require formatted entries such as phone numbers and dates, ensuring consistent and structured user input.

Baklava is designed to provide stable and general low-level building blocks. This approach helps maintain the flexibility and adaptability of our components, allowing developers the freedom to leverage specialized features such as input masking by incorporating external libraries like [IMask](https://imask.js.org) or [Maskito](https://maskito.dev).

Successfully integrating third-party masking libraries with Baklava's components involves understanding how these libraries interact with web components. Libraries that provide granular control are especially well-suited for this. For example, IMask and Maskito are compatible with web components due to their APIs which allow direct interactions with the input elements, even those nested within shadow DOM. This capability ensures that the masking logic is applied correctly without interfering with the component's internal structure or lifecycle.

### Using with JavaScript/TypeScript

For classic web development, implementing input masking involves selecting the underlying input element and applying the masking logic directly.

```html
<bl-input
  id="inputPhoneUSA"
  size="large"
  icon="phone"
  label="Phone Number"
  placeholder="Enter phone number"
  name="phone-usa"
  autofocus
></bl-input>

<script>
  const options = {
    mask: '+1 (000) 000-0000',
  };

  const element = document.getElementById('inputPhoneUSA');
  const input = element.shadowRoot.querySelector('input');

  IMask(input, options);
</script>
```

### Using with React and Vue

React and Vue pose unique challenges for integrating direct DOM manipulations. These issues stem from their respective rendering mechanisms which can interfere with how external libraries manipulate the DOM. Addressing these requires syncing the DOM updates manually with the component's state or lifecycle. Usage of a [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) per masked component is a controlled and precise way to handle these updates. In this manner masking behavior can be applied when appropriate and also removed when necessary.

#### React example:

```jsx
import { BlInput } from '@trendyol/baklava/dist/baklava-react';
import { useIMaskWithObserver } from '../../hooks';

export default function InputPhoneUSA() {
  const ref = useIMaskWithObserver({
    mask: '+1 (000) 000-0000',
  });

  return (
    <BlInput
      ref={ref}
      size="large"
      icon="phone"
      label="Phone Number"
      placeholder="Enter phone number"
      name="phone-usa"
      autofocus
    />
  );
}
```

#### Vue example:

```html
<template>
  <bl-input
    ref="blInputRef"
    size="large"
    icon="phone"
    label="Phone Number"
    placeholder="Enter phone number"
    name="phone-usa"
    autofocus
  />
</template>

<script>
  import { defineComponent } from 'vue';
  import { useIMaskWithObserver } from '../../hooks/useIMaskWithObserver';

  export default defineComponent({
    name: 'InputPhoneUSA',
    setup() {
      const { el: blInputRef } = useIMaskWithObserver({
        mask: '+1 (000) 000-0000',
      });

      return {
        blInputRef,
      };
    },
  });
</script>
```

In both examples, the `useIMaskWithObserver` hook is a custom hook that combines IMask with a MutationObserver to ensure that the masking behavior is applied correctly and consistently. Respective example implementations for such an approach can be found in [here (React)](https://github.com/trendyol/baklava/tree/next/examples/input-mask-react/src/examples/imask/hooks/useIMaskWithObserver.js) and [here (Vue)](https://github.com/trendyol/baklava/tree/next/examples/input-mask-vue/src/examples/imask/hooks/useIMaskWithObserver.js).

Also we provide usage examples for JS/TS, React and Vue for both IMask and Maskito. You can check the examples from the links below:

- [JS/TS example repository](https://github.com/trendyol/baklava/tree/next/examples/input-mask) - [Stackblitz](https://stackblitz.com/github/trendyol/baklava/tree/next/examples/input-mask)
- [React example repository](https://github.com/trendyol/baklava/tree/next/examples/input-mask-react) - [Stackblitz](https://stackblitz.com/github/trendyol/baklava/tree/next/examples/input-mask-react)
- [Vue example repository](https://github.com/trendyol/baklava/tree/next/examples/input-mask-vue) - [Stackblitz](https://stackblitz.com/github/trendyol/baklava/tree/next/examples/input-mask-vue)

We also want to enrich our examples with more compatible masking libraries. If you have any suggestions or contributions, please feel free to open an issue or PR.

## Reference

<ArgsTable of="bl-input" />
